package com.hwtx.form.domain.handle;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hwtx.form.annotation.Action;
import com.hwtx.form.annotation.Log;
import com.hwtx.form.annotation.OperationType;
import com.hwtx.form.annotation.Property;
import com.hwtx.form.domain.FormConstants;
import com.hwtx.form.domain.def.FormDef;
import com.hwtx.form.domain.def.FormProperties;
import com.hwtx.form.domain.def.GenFormContextHolder;
import com.hwtx.form.domain.def.Select;
import com.hwtx.form.domain.def.meta.Api;
import com.hwtx.form.domain.def.meta.ChainedSelect;
import com.hwtx.form.domain.def.meta.Component;
import com.hwtx.form.domain.def.meta.config.FormComponentConfig;
import com.hwtx.form.domain.handle.datamodel.DataModel;
import com.hwtx.form.domain.handle.datamodel.DataModelDetail;
import com.hwtx.form.domain.handle.i18n.I18nConfig;
import com.hwtx.form.domain.listener.DataPermValueChangeEvent;
import com.hwtx.form.domain.listener.EventPublisher;
import com.hwtx.form.domain.listener.ResourceChangeEvent;
import com.hwtx.form.domain.repo.FormRepo;
import com.hwtx.form.domain.service.RacService;
import com.hwtx.form.domain.service.ResourceService;
import com.hwtx.form.domain.vo.FormListVo;
import com.hwtx.form.domain.vo.ResultVo;
import com.hwtx.form.dto.FormDefDto;
import com.hwtx.form.dto.FormQuery;
import com.hwtx.form.persistence.entity.SysFunctionResourceMapping;
import com.hwtx.form.persistence.entity.SysRoleDataPermPro;
import com.hwtx.form.persistence.mapper.SysFunctionResourceMappingMapper;
import com.hwtx.form.persistence.mapper.SysRoleDataPermProMapper;
import com.hwtx.form.util.FormUtil;
import com.hwtx.form.util.JacksonUtil;
import io.geekidea.boot.auth.cache.LoginCache;
import io.geekidea.boot.config.properties.BootProperties;
import io.geekidea.boot.framework.exception.BusinessException;
import io.geekidea.boot.system.entity.SysMenu;
import io.geekidea.boot.system.enums.SysMenuType;
import io.geekidea.boot.system.query.SysMenuQuery;
import io.geekidea.boot.system.vo.DataPermissionConfig;
import io.geekidea.boot.system.vo.SysMenuTreeVo;
import jakarta.annotation.Resource;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.*;
import static com.hwtx.form.domain.handle.datamodel.DataModel.isSqlModel;
import static com.hwtx.form.util.FormUtil.*;
import static com.hwtx.form.util.JacksonUtil.map2Oject;

@Slf4j
@org.springframework.stereotype.Component
public class ResourceFormHandleAction {

    @Resource
    ResourceService resourceService;
    @Resource
    EventPublisher eventPublisher;
    @Resource
    private FormRepo formRepo;
    @Resource
    private ServerProperties webApplicationContext;
    @Resource
    private RacService racService;
    @Resource
    private BootProperties bootProperties;
    @Resource
    private SysRoleDataPermProMapper sysRoleDataPermProMapper;
    @Resource
    private SysFunctionResourceMappingMapper sysFunctionResourceMappingMapper;
    @Resource
    private ApplicationContext applicationContext;
    @Resource
    private I18nConfig i18nConfig;

    /**
     * 过滤List中的Map，移除空Map和包含空值的Map
     */
    private static List<?> filterEmptyMaps(List<?> list) {
        if (list == null || list.isEmpty()) {
            return Collections.emptyList();
        }

        if (list.stream().anyMatch(map -> !(map instanceof Map))) {
            return list;
        }

        return list.stream().filter(map -> map instanceof Map).map(map -> (Map<?, ?>) map).filter(map -> !map.isEmpty()) // 过滤掉null和空Map
                .map(ResourceFormHandleAction::filterEmptyValues)          // 过滤Map中的空值
                .filter(map -> !map.isEmpty())              // 再次过滤可能变空的Map
                .collect(Collectors.toList());
    }

    /**
     * 过滤Map中的空值（null、空字符串、空集合等）
     */
    private static Map<?, ?> filterEmptyValues(Map<?, ?> map) {
        if (map == null || map.isEmpty()) {
            return Collections.emptyMap();
        }

        return map.entrySet().stream().filter(entry -> {
            Object value = entry.getValue();
            return !isEmptyValue(value);
        }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    /**
     * 判断值是否为空
     */
    private static boolean isEmptyValue(Object value) {
        if (value == null) {
            return true;
        }
        if (value instanceof String) {
            return ((String) value).isEmpty();
        }
        if (value instanceof Collection) {
            return ((Collection<?>) value).isEmpty();
        }
        if (value instanceof Map) {
            return ((Map<?, ?>) value).isEmpty();
        }
        if (value instanceof Object[]) {
            return ((Object[]) value).length == 0;
        }
        // 可以继续添加其他类型的空值判断
        return false;
    }

    @Action
    @Log(type = OperationType.UPDATE, value = "保存角色数据权限属性")
    public boolean saveRoleForDataPermPro(Map<String, Object> formData) {
        SysRoleDataPermPro sysRoleDataPermPro = map2Oject(formData, SysRoleDataPermPro.class);
        if (sysRoleDataPermPro.getMenuId() == null) {
            throw new BusinessException("请选择菜单");
        }
        SysMenu sysMenu = resourceService.getById(sysRoleDataPermPro.getMenuId());
        if (sysMenu == null) {
            throw new BusinessException("菜单不存在");
        }
        if (StringUtils.isBlank(sysMenu.getDataPermConfig())) {
            return true;
        }
        List<DataPermissionConfig.RDataPermissionConfig> dataPermConfig = JacksonUtil.json2Array(sysMenu.getDataPermConfig(), new TypeReference<>() {
        });
        Map<String, Object> formDataMap = new HashMap<>();
        dataPermConfig.forEach(config -> {
            String name = config.getName();
            if (StringUtils.isNotEmpty(config.getModelName())) {
                name = Joiner.on(",").join(getSqlDataModelInputs(config.getModelName()));
            }
            String requestParam = Joiner.on("_").join(name.split(","));
            Object value = formData.get(requestParam);
            if (value != null && StringUtils.isNotEmpty(value.toString())) {
                SysRoleDataPermPro.ResourceDataPermConfig resourceDataPermConfig = new SysRoleDataPermPro.ResourceDataPermConfig();
                resourceDataPermConfig.setModelName(config.getModelName());
                resourceDataPermConfig.setValue(value);
                if (value instanceof List) {
                    resourceDataPermConfig.setValue(filterEmptyMaps((List<?>) value));
                }
                formDataMap.put(requestParam, resourceDataPermConfig);
            }
        });
        sysRoleDataPermPro.setDataPermConfig(JacksonUtil.object2json(formDataMap));
        if (sysRoleDataPermPro.getId() == null) {
            sysRoleDataPermProMapper.insert(sysRoleDataPermPro);
        } else {
            sysRoleDataPermProMapper.updateById(sysRoleDataPermPro);
        }
        eventPublisher.publishEvent(new DataPermValueChangeEvent(this, sysRoleDataPermPro.getMenuId()));
        return true;
    }

    @Action
    @Log(type = OperationType.GET, value = "获取组件配置")
    public String loadPermFormSegment(Map<String, Object> formData) {

        Long resourceId = getLongPostValue(formData, "id");
        if (Objects.equals(getStrPostValue(formData, "type"), "output")) {
            String modelName = getStrPostValue(formData, "modelName");
            DataModel dataModel = getDataModel(modelName);
            if (!isSqlModel(dataModel.getModelType())) {
                throw new BusinessException("数据模型非SQL类型");
            }
            List<String> outputParams = FormUtil.getDataModelDetails(modelName).stream().map(DataModelDetail::getName).toList();
            if (CollectionUtils.isEmpty(outputParams)) {
                return "{}";
            }
            SysMenu sysMenu = resourceService.getById(resourceId);
            Map<String, FormComponentConfig.ParamMapping> outputMapping = new HashMap<>();
            if (sysMenu != null && StringUtils.isNotEmpty(sysMenu.getDataPermConfig())) {
                sysMenu.getRDataPermConfig().forEach(item -> {
                    if (CollectionUtils.isNotEmpty(item.getOutput())) {
                        item.getOutput().forEach(output -> outputMapping.put(output.getKey(), output));
                    }
                });
            }
            List<Component> formBody = Lists.newArrayList();
            StringBuilder values = new StringBuilder();
            AtomicInteger i = new AtomicInteger(1);
            Component.Form form = new Component.Form();
            formBody.addAll(outputParams.stream().map(key -> {
                Component.Group group = new Component.Group();
                values.append("${event.data.").append(key).append("}").append(":").append("${event.data.").append(key).append("Mapping}").append(",");
                List<Component> components = Lists.newArrayList();
                Component.Input input = new Component.Input();
                input.setType(Component.INPUT_TEXT);
                input.setFormBatProp();
                input.setName(key);
                input.setValue(key);
                input.setDisabled(true);
                if (i.getAndIncrement() == 1) {
                    input.setLabel("输出");
                } else {
                    input.setLabel("false");
                }
                components.add(input);
                input = new Component.Input();
                input.setType(Component.INPUT_TEXT);
                input.setName(key + "Mapping");
                input.setPlaceholder("映射别名");
                input.setLabel(false);
                if (outputMapping.containsKey(key)) {
                    input.setValue(outputMapping.get(key).getAlias());
                }
                components.add(input);
                group.setBody(components);
                return group;
            }).toList());
            form.setBody(formBody);
            Component.ChangeEvent changeEvent = new Component.ChangeEvent();
            values.deleteCharAt(values.length() - 1);
            changeEvent.setActions(List.of(Component.Action.getSetValueAction(values.toString(), "output")));
            Component.OnEvent onEvent = new Component.OnEvent();
            onEvent.setChange(changeEvent);
            form.setOnEvent(onEvent);
            return JacksonUtil.object2json(form);
        }
        if (resourceId == null) {
            throw new BusinessException("菜单ID不能为空");
        }
        Long roleId = getLongPostValue(formData, "roleId");
        if (roleId == null) {
            throw new BusinessException("角色ID不能为空");
        }
        String localeStr = getStrPostValue(formData, FormConstants.LOCALE_PARAM);
        Locale locale;
        if (StringUtils.isNotBlank(localeStr)) {
            locale = Locale.forLanguageTag(localeStr);
        } else {
            locale = Locale.getDefault();
        }
        SysMenu sysMenu = resourceService.getById(resourceId);
        if (sysMenu == null) {
            throw new BusinessException("角色不存在");
        }
        if (StringUtils.isEmpty(sysMenu.getDataPermConfig())) {
            return "{}";
        }
        List<DataPermissionConfig> dataPermConfig = JacksonUtil.json2Array(sysMenu.getDataPermConfig(), new TypeReference<>() {
        });
        List<Component> components = Lists.newArrayList();
        SysRoleDataPermPro sysRoleDataPermPro = sysRoleDataPermProMapper.selectOne(new LambdaQueryWrapper<SysRoleDataPermPro>().eq(SysRoleDataPermPro::getMenuId, resourceId).eq(SysRoleDataPermPro::getRoleId, roleId));
        Map<String, Object> data = new HashMap<>();
        if (sysRoleDataPermPro != null && StringUtils.isNotBlank(sysRoleDataPermPro.getDataPermConfig())) {
            data.putAll(JacksonUtil.json2Map(sysRoleDataPermPro.getDataPermConfig()));
        }
        GenFormContextHolder.init(GenFormContextHolder.GenFormInfo.builder().domain(bootProperties.getDomain()).contextPath(FormUtil.getUrlContext(webApplicationContext)).build());
        dataPermConfig.forEach(config -> {
            FormComponentType type = FormComponentType.of(config.getController());
            if (type == null) {
                return;
            }
            String name = config.getName();
            if (StringUtils.isNotEmpty(config.getModelName())) {
                name = Joiner.on(",").join(getSqlDataModelInputs(config.getModelName()));
            }
            String[] names = name.split(",");
            switch (type) {
                case FormComponentType.FORM_COM_2_GROUP_CHAINED_SELECT:
                case FormComponentType.FORM_COM_3_GROUP_CHAINED_SELECT:
                case FormComponentType.FORM_COM_4_GROUP_CHAINED_SELECT:
                    components.addAll(buildGroupChainSelect(type, names, config, data));
                    break;
                case FormComponentType.FORM_COM_SINGLE_SELECT:
                    components.addAll(buildSelect(config, data));
                    break;
                case FORM_COM_CHAINED_SELECT:
                    components.addAll(buildChainSelect(config, data));
                    break;
                default:
                    throw new BusinessException("不支持的组件类型");
            }
        });
        components.add(Component.Hidden.buildHidden("roleId", roleId));
        components.add(Component.Hidden.buildHidden("menuId", resourceId));
        if (sysRoleDataPermPro != null) {
            components.add(Component.Hidden.buildHidden("id", sysRoleDataPermPro.getId()));
        }
        Component.Form form = new Component.Form();
        form.setWrapWithPanel(false);
        form.setCanAccessSuperData(false);
        form.setApi(Api.builder().method(HTTP_POST).url(bootProperties.getDomain() + FormUtil.getUrlContext(webApplicationContext) + "/form/app/resource/saveRoleForDataPermPro").build());
        form.setBody(components);
        return i18nConfig.handlePageI18n(JacksonUtil.object2json(form), locale);
    }

    private Collection<? extends Component> buildSelect(DataPermissionConfig config, Map<String, Object> data) {
        Component.Select select = new Component.Select();
        select.setName(config.getName());
        select.setClearable(true);
        select.setSearchable(true);
        select.setSize("sm");
        select.setSource(ChainedSelect.getSource(config.getApiCode(), null));
        select.setValue(data.get(config.getName()));
        select.setRequired(true);
        select.setLabel(config.getRemark());
        select.setFormBatProp();
        return List.of(select);
    }

    @Log(type = OperationType.GET, value = "获取角色对应的菜单树")
    @Action
    public FormListVo<SysMenuTreeVo> getRoleForMenuTree(Map<String, Object> formData) {
        FormListVo<SysMenuTreeVo> tableData = new FormListVo<>();
        Long roleId = FormUtil.getLongPostValue(formData, "roleId");
        String keyword = FormUtil.getStrPostValue(formData, "keyword");
        tableData.setRows(racService.getRoleForMenus(roleId, keyword));
        return tableData;
    }

    @Log(type = OperationType.UPDATE, value = "删除权限配置")
    @Action
    public boolean removePermConfig(Map<String, Object> formData) {
        Long menuId = FormUtil.getLongPostValue(formData, "id");
        Long roleId = FormUtil.getLongPostValue(formData, "roleId");
        boolean ret = sysRoleDataPermProMapper.delete(new LambdaQueryWrapper<SysRoleDataPermPro>().eq(SysRoleDataPermPro::getMenuId, menuId).eq(SysRoleDataPermPro::getRoleId, roleId)) > 0;
        eventPublisher.publishEvent(new DataPermValueChangeEvent(this, menuId));
        return ret;
    }

    @Log(type = OperationType.GET, value = "批量导入并更新权限数据")
    @Action("batImportAndUpdate")
    @Transactional
    public ResultVo batImportAndUpdatePerms(Map<String, Object> formData) {
        String code = FormUtil.getStrPostValue(formData, "code");
        String pId = FormUtil.getStrPostValue(formData, "pId");
        if (StringUtils.isBlank(code) || StringUtils.isBlank(pId)) {
            return ResultVo.fail("父节点或表单类型不能为空");
        }
        FormQuery formQuery = new FormQuery();
        int index = code.indexOf("-");
        AtomicBoolean hasSubCode = new AtomicBoolean(false);
        String type = code;
        if (index > 0) {
            type = code.substring(0, index);
            hasSubCode.set(true);
        }
        formQuery.setTypes(List.of(type));
        List<FormProperties> formProperties = formRepo.listFormInfo(formQuery);
        if (CollectionUtils.isEmpty(formProperties)) {
            return ResultVo.fail("未找到对应的表单");
        }
        String context = getUrlContext(webApplicationContext);
        Map<Long, FormElementInfo> loadExtInfo = new HashMap<>();
        Map<Long, List<FormElementInfo>> loadedForm = new HashMap<>();
        Map<Long, ChangedFormDef> formChanged = new HashMap<>();
        formProperties.stream().filter(formInfo -> {
            if (hasSubCode.get()) {
                return formInfo.getName().startsWith(code);
            }
            return true;
        }).forEach(formInfo -> {
            FormDef formDef = formRepo.getFormDef(formInfo.getFormId());
            if (formDef == null) {
                return;
            }
            JsonNode rootNode = formDef.getJsonRoot();
            AUTH_ELEMENTS.stream().map(key -> getFormElementInfos(rootNode, formDef, key)).flatMap(Collection::stream).forEach(item -> {
                String newAuthCode = FormUtil.getFormAuthCode(formDef, item);
                boolean codeChanged = item.getCode() != null && !item.getCode().equals(newAuthCode);
                item.setCode(newAuthCode);
                if (CollectionUtils.isNotEmpty(item.getUrls()) && (!item.getNode().has(AUTH_KEY) || codeChanged)) {
                    ((ObjectNode) item.getNode()).set(AUTH_KEY, TextNode.valueOf(item.getCode()));
                    formChanged.putIfAbsent(formDef.getFormId(), new ChangedFormDef(formDef, rootNode));
                }
                AtomicBoolean added = new AtomicBoolean(false);
                loadedForm.compute(formDef.getFormId(), (k, v) -> {
                    if (v == null) {
                        v = new ArrayList<>();
                    }
                    if (v.stream().anyMatch(e -> Objects.equals(e.getCode(), item.getCode()))) {
                        return v;
                    }
                    v.add(item);
                    added.set(true);
                    return v;
                });
                if (added.get()) {
                    for (FormUrlInfo urlInfo : item.getUrls()) {
                        String itemUrl = urlInfo.getUrl().getPath();
                        Long loadFormId = getLoadFormId(itemUrl);
                        if (loadFormId != null && !loadExtInfo.containsKey(loadFormId)) {
                            loadExtInfo.put(loadFormId, item);
                        }
                    }
                }
            });
            loadExtInfo.forEach((fId, v) -> {
                List<FormElementInfo> loadedItems = loadedForm.get(fId);
                if (loadedItems == null) {
                    return;
                }
                loadedItems.stream().filter(item -> !item.getName().equalsIgnoreCase(AuthElementTypeEnum.Button.getType())).forEach(item -> {
                    Map<Long, Set<FormUrlInfo>> addUrls = v.getAddUrls();
                    if (MapUtils.isEmpty(addUrls)) {
                        addUrls = new HashMap<>();
                    }
                    addUrls.compute(fId, (k, urls) -> {
                        if (urls == null) {
                            urls = Sets.newHashSet();
                        }
                        urls.addAll(item.getUrls());
                        return urls;
                    });
                    v.getAddUrls().putAll(addUrls);
                    item.getUrls().clear();
                });
            });
        });
        updateResource(Long.parseLong(pId), loadedForm, context, formChanged);
        return ResultVo.success();
    }

    private void updateResource(Long parentId, Map<Long, List<FormElementInfo>> loadedForm, String context, Map<Long, ChangedFormDef> formChanged) {
        Set<DelResource> delMappings = Sets.newHashSet();
        List<SysFunctionResourceMapping> addMappings = Lists.newArrayList();
        Map<Long, List<String>> incomingCode = Maps.newHashMap();
        Set<FormUtil.FormUrlInfo> handledUrls = Sets.newHashSet();
        loadedForm.forEach((formId, items) -> {
            if (items.stream().allMatch(item -> CollectionUtils.isEmpty(item.getUrls()) && MapUtils.isEmpty(item.getAddUrls()))) {
                formChanged.remove(formId);
                return;
            }
            items.forEach(item -> {
                if (CollectionUtils.isNotEmpty(item.getUrls()) || MapUtils.isNotEmpty(item.getAddUrls())) {
                    Collection<FormUtil.FormUrlInfo> intersection = CollectionUtils.intersection(handledUrls, item.getUrls());
                    if (CollectionUtils.isNotEmpty(intersection)) {
                        item.getUrls().removeAll(intersection);
                    }
                    handledUrls.addAll(item.getUrls());
                    BuildResourceRet ret = buildResource(item, parentId, formId, context);
                    if (CollectionUtils.isNotEmpty(ret.getAddResources())) {
                        addMappings.addAll(ret.getAddResources());
                    }
                    if (ret.getDeleteResources() != null) {
                        delMappings.addAll(ret.getDeleteResources());
                    }
                } else {
                    ((ObjectNode) item.getNode()).remove(AUTH_KEY);
                }
                incomingCode.compute(formId, (k, v) -> {
                    if (v == null) {
                        v = Lists.newArrayList();
                    }
                    v.add(item.getCode());
                    return v;
                });
            });
        });
        formChanged.forEach((formId, formInfo) -> {
            FormDefDto formDefDto = new FormDefDto();
            formDefDto.setContent(formInfo.rootNode.toPrettyString());
            formDefDto.setId(formId);
            formDefDto.setName(formInfo.formDef().getName());
            formDefDto.setType(formInfo.formDef().getType());
            try {
                formRepo.updateFormDef(formDefDto);
            } catch (Exception e) {
                log.error("更新表单权限标识失败,formId = {}", formId, e);
                throw new BusinessException("更新表单权限标识失败,formId = " + formId);
            }
            log.info("更新表单权限标识成功,formId = {}", formId);
        });
        if (!delMappings.isEmpty()) {
            for (DelResource mapping : delMappings) {
                LambdaQueryWrapper<SysFunctionResourceMapping> lambdaQueryWrapper = new LambdaQueryWrapper<>();
                if (mapping.formId() != null) {
                    lambdaQueryWrapper.eq(SysFunctionResourceMapping::getFromFormId, mapping.formId());
                }
                lambdaQueryWrapper.eq(SysFunctionResourceMapping::getResourceId, mapping.resourceId()).in(SysFunctionResourceMapping::getUrl, mapping.urls().stream().map(url -> url.startsWith(context) ? url.substring(context.length()) : url).toList());
                sysFunctionResourceMappingMapper.delete(lambdaQueryWrapper);
            }
        }
        sysFunctionResourceMappingMapper.insert(addMappings);
        SysMenuQuery query = new SysMenuQuery();
        loadedForm.keySet().forEach(formId -> {
            query.setParentId(parentId);
            query.setTypes(List.of(SysMenuType.FUN_PERMISSION.getCode()));
            query.setFId(formId);
            List<String> codes = incomingCode.get(formId);
            resourceService.getPermissions(query).stream().filter(resource -> codes != null && !codes.contains(resource.getCode())).forEach(resource -> {
                resourceService.deleteSysMenu(resource.getId());
                sysFunctionResourceMappingMapper.delete(new LambdaQueryWrapper<SysFunctionResourceMapping>().eq(SysFunctionResourceMapping::getResourceId, resource.getId()));
                eventPublisher.publishEvent(new ResourceChangeEvent(this, resource.getId(), ResourceChangeEvent.ChangeType.DELETE));
            });
        });
    }

    @Log(type = OperationType.GET, value = "获取资源列表")
    @Action(properties = {@Property(name = AUTH_KEY, value = LIST_URL_KEY)})
    public Select.NestedSelect resourceList() {
        Select.NestedSelect select = new Select.NestedSelect();
        Select.NestedOption parent = new Select.NestedOption();
        parent.setLabel(SysMenuType.ROOT.getDesc());
        parent.setValue(Integer.toString(SysMenuType.ROOT.getCode()));
        List<SysMenuTreeVo> menus = resourceService.getResourceTree(new SysMenuQuery());
        parent.setChildren(menus.stream().map(menu -> {
            Select.NestedOption option = new Select.NestedOption();
            option.setLabel(menu.getName());
            option.setValue(menu.getId().toString());
            option.setChildren(menu.getChildren().stream().filter(child -> Objects.equals(child.getIsShow(), Boolean.TRUE)).map(child -> {
                Select.NestedOption childOption = new Select.NestedOption();
                childOption.setLabel(child.getName());
                childOption.setValue(child.getId().toString());
                return childOption;
            }).collect(Collectors.toList()));
            return option;
        }).toList());
        select.setOptions(Lists.newArrayList(parent));
        return select;
    }

    private BuildResourceRet buildResource(FormElementInfo item, Long pId, Long formId, String context) {
        BuildResourceRet ret = new BuildResourceRet();
        SysMenu sysMenu = new SysMenu();
        sysMenu.setStatus(true);
        sysMenu.setFId(formId);
        sysMenu.setParentId(pId);
        sysMenu.setType(SysMenuType.FUN_PERMISSION.getCode());
        SysMenu existMenu = resourceService.getOne(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getCode, item.getCode()));
        String name = getResourceName(item, context);
        Long resourceId;
        List<FormElementAddUrl> addUrls = Lists.newArrayList();
        if (existMenu == null) {
            sysMenu.setCode(item.getCode());
            sysMenu.setName(name);
            sysMenu.setCreateId(LoginCache.get().getUsername());
            sysMenu.setUpdateId(LoginCache.get().getUsername());
            sysMenu.setCreateTime(new Date());
            sysMenu.setUpdateTime(new Date());
            resourceService.addSysMenu(sysMenu);
            resourceId = sysMenu.getId();
            if (MapUtils.isNotEmpty(item.getAddUrls())) {
                item.getAddUrls().forEach((k, v) -> {
                    if (CollectionUtils.isEmpty(v)) {
                        return;
                    }
                    UrlConvertRet urlInfos = convertUrlInfo(v, formId);
                    urlInfos.getFromFormIdAndUrls().forEach((f, urls) -> addUrls.add(FormElementAddUrl.builder().fromFormId(f).urls(urls).build()));
                    addUrls.add(FormElementAddUrl.builder().fromFormId(k).urls(urlInfos.getNoFromFormIds()).build());
                });
            }
            if (CollectionUtils.isNotEmpty(item.getUrls())) {
                UrlConvertRet urlInfos = convertUrlInfo(item.getUrls(), formId);
                urlInfos.getFromFormIdAndUrls().forEach((f, urls) -> addUrls.add(FormElementAddUrl.builder().fromFormId(f).urls(urls).build()));
                addUrls.add(FormElementAddUrl.builder().urls(urlInfos.getNoFromFormIds()).build());
            }
        } else {
            resourceId = existMenu.getId();
            List<SysFunctionResourceMapping> mappings = sysFunctionResourceMappingMapper.selectList(new LambdaQueryWrapper<SysFunctionResourceMapping>().eq(SysFunctionResourceMapping::getResourceId, resourceId));
            Set<String> existUrls = Sets.newHashSet();
            Map<Long, Set<String>> existFormAddUrls = Maps.newHashMap();
            mappings.forEach(mapping -> {
                if (mapping.getFromFormId() == null) {
                    existUrls.add(context + mapping.getUrl());
                } else {
                    existFormAddUrls.computeIfAbsent(mapping.getFromFormId(), k -> Sets.newHashSet()).add(context + mapping.getUrl());
                }
            });
            UrlConvertRet urlInfos = convertUrlInfo(item.getUrls(), formId);
            Collection<String> delUrls = CollectionUtils.subtract(existUrls, urlInfos.getNoFromFormIds());
            if (!delUrls.isEmpty()) {
                ret.ofDel(resourceId, delUrls);
            }
            if (MapUtils.isNotEmpty(urlInfos.getFromFormIdAndUrls())) {
                urlInfos.getFromFormIdUrlInfos().forEach((k, v) -> {
                    if (item.getAddUrls().containsKey(k)) {
                        item.getAddUrls().get(k).addAll(v);
                    } else {
                        item.getAddUrls().put(k, v);
                    }
                });
            }
            Collection<String> noAddFormAdds = CollectionUtils.subtract(urlInfos.getNoFromFormIds(), existUrls);
            if (!noAddFormAdds.isEmpty()) {
                addUrls.add(FormElementAddUrl.builder().urls(Sets.newHashSet(noAddFormAdds)).build());
            }
            if (MapUtils.isNotEmpty(item.getAddUrls())) {
                item.getAddUrls().forEach((k, incomingUrls) -> {
                    UrlConvertRet addUrlInfos = convertUrlInfo(incomingUrls, k, formId);
                    if (MapUtils.isEmpty(existFormAddUrls) || CollectionUtils.isEmpty(existFormAddUrls.get(k))) {
                        if (MapUtils.isNotEmpty(addUrlInfos.getFromFormIdAndUrls())) {
                            addUrlInfos.getFromFormIdAndUrls().forEach((f, urls) -> addUrls.add(FormElementAddUrl.builder().fromFormId(f).urls(urls).build()));
                        }
                    } else {
                        addUrlInfos.getFromFormIdAndUrls().forEach((f, urls) -> {
                            Set<String> addFormUrls = existFormAddUrls.get(f);
                            if (CollectionUtils.isEmpty(addFormUrls)) {
                                addUrls.add(FormElementAddUrl.builder().fromFormId(f).urls(Sets.newHashSet(urls)).build());
                            } else {
                                Collection<String> addFormDels = CollectionUtils.subtract(addFormUrls, urls);
                                if (!addFormDels.isEmpty()) {
                                    ret.ofDel(f, resourceId, addFormDels);
                                }
                                Collection<String> addFormAdds = CollectionUtils.subtract(urls, addFormUrls);
                                if (!addFormAdds.isEmpty()) {
                                    addUrls.add(FormElementAddUrl.builder().fromFormId(f).urls(Sets.newHashSet(addFormAdds)).build());
                                }
                            }
                        });
                    }
                });
            }
            if (StringUtils.isNotEmpty(name) && StringUtils.isEmpty(existMenu.getName())) {
                existMenu.setName(name);
                existMenu.setUpdateId(LoginCache.get().getUsername());
                existMenu.setUpdateTime(new Date());
                resourceService.updateById(existMenu);
            }
        }
        addUrls.forEach(addUrl -> addUrl.urls().forEach(url -> {
            SysFunctionResourceMapping sysFunctionResourceMapping = new SysFunctionResourceMapping();
            sysFunctionResourceMapping.setResourceId(resourceId);
            sysFunctionResourceMapping.setFromFormId(addUrl.fromFormId());
            if (url.startsWith(context)) {
                sysFunctionResourceMapping.setUrl(url.substring(context.length()));
            } else {
                sysFunctionResourceMapping.setUrl(url);
            }
            sysFunctionResourceMapping.setCreateId(LoginCache.get().getUsername());
            sysFunctionResourceMapping.setUpdateId(LoginCache.get().getUsername());
            ret.ofAdd(sysFunctionResourceMapping);
        }));
        return ret;
    }

    private String getResourceName(FormElementInfo item, String context) {
        String name = item.getLabel();
        if (StringUtils.isEmpty(name) && item.getUrls() != null && item.getUrls().size() == 1) {
            String url = item.getUrls().iterator().next().getUrl().getPath();
            if (url.startsWith(context)) {
                url = url.substring(context.length());
            }
            if (!DEFAULT_URLS.contains(url)) {
                int start = url.indexOf("/form/app/");
                if (start >= 0) {
                    String actionName = url.substring(url.lastIndexOf("/") + 1);
                    int end = url.indexOf("/", start + 10);
                    String beanName = url.substring(start + "/form/app/".length(), end);
                    Object bean = applicationContext.getBean(beanName);
                    name = FormUtil.getFormActionComment(bean.getClass(), actionName);
                } else {
                    Map<String, Object> controllers = applicationContext.getBeansWithAnnotation(RestController.class);
                    for (Object bean : controllers.values()) {
                        String label = getControllerComment(bean.getClass(), url);
                        if (label != null) {
                            name = label;
                            break;
                        }
                    }
                }
            }
        }
        return name;
    }

    private UrlConvertRet convertUrlInfo(Collection<FormUrlInfo> urlInfos, Long rootFormId) {
        return convertUrlInfo(urlInfos, null, rootFormId);
    }

    private UrlConvertRet convertUrlInfo(Collection<FormUrlInfo> urlInfos, Long _formId, Long rootFormId) {
        Map<Long, Set<String>> formIdAndUrls = Maps.newHashMap();
        Set<String> noFromFormIds = Sets.newHashSet();
        Set<FormUrlInfo> noFromFormIdUrlInfos = Sets.newHashSet();
        Map<Long, Set<FormUrlInfo>> fromFormIdUrlInfos = Maps.newHashMap();
        urlInfos.forEach(url -> {
            String path = url.getUrl().getPath();
            Long ffId = null;
            String formId = url.getParams().get(FORM_ID);
            if (formId == null) {
                formId = url.getParams().get(SHORT_FORM_ID);
            }
            if (StringUtils.isNotEmpty(formId) && formId.matches("\\d+")) {
                ffId = Long.parseLong(formId);
            }
            if (ffId != null && ffId.equals(rootFormId)) {
                ffId = null;
            }
            if (ffId == null && _formId != null) {
                ffId = _formId;
            }
            Long loadFormId = FormUtil.getLoadFormId(path);
            if (loadFormId != null) {
                ffId = loadFormId;
            }
            if (ffId != null) {
                formIdAndUrls.compute(ffId, (fId, urls) -> {
                    if (urls == null) {
                        urls = Sets.newHashSet();
                    }
                    urls.add(path);
                    return urls;
                });
                fromFormIdUrlInfos.compute(ffId, (fId, urls) -> {
                    if (urls == null) {
                        urls = Sets.newHashSet();
                    }
                    urls.add(url);
                    return urls;
                });
            } else {
                noFromFormIds.add(path);
                noFromFormIdUrlInfos.add(url);
            }
        });
        return UrlConvertRet.builder().fromFormIdAndUrls(formIdAndUrls).noFromFormIdUrlInfos(noFromFormIdUrlInfos)
                .fromFormIdUrlInfos(fromFormIdUrlInfos).noFromFormIds(noFromFormIds).build();
    }

    private List<Component> buildChainSelect(DataPermissionConfig config, Map<String, Object> data) {
        ChainedSelect chainedSelect = new ChainedSelect();
        chainedSelect.setSource(ChainedSelect.getSource(config.getApiCode(), List.of("${parentId}")));
        chainedSelect.setLabel(config.getRemark());
        chainedSelect.setSearchable(true);
        chainedSelect.setFormBatProp();
        chainedSelect.setName(config.getName());
        chainedSelect.setRequired(true);
        Object vv = data.get(config.getName());
        if (vv != null) {
            if (vv instanceof Map) {
                chainedSelect.setValue(((Map<?, ?>) vv).get("value"));
            } else {
                chainedSelect.setValue(vv);
            }
        }
        return List.of(chainedSelect);
    }

    private List<Component> buildGroupChainSelect(FormComponentType type, String[] names, DataPermissionConfig config, Map<String, Object> data) {
        int slot = 0;
        if (type == FormComponentType.FORM_COM_2_GROUP_CHAINED_SELECT) {
            slot = 2;
        } else if (type == FormComponentType.FORM_COM_3_GROUP_CHAINED_SELECT) {
            slot = 3;
        } else if (type == FormComponentType.FORM_COM_4_GROUP_CHAINED_SELECT) {
            slot = 4;
        }
        List<Component> items = new ArrayList<>();
        String parentId = "";
        int start = 0;
        if (names.length < slot) {
            start = slot - names.length;
        }
        for (int i = 0; i < slot; i++) {
            Component.Select select = new Component.Select();
            String name;
            if (i >= start) {
                name = names[i - start];
            } else {
                name = "chainName_" + i;
            }
            select.setName(name);
            select.setClearable(true);
            select.setSearchable(true);
            select.setSize("sm");
            List<String> values = null;
            if (i != 0) {
                values = List.of(parentId);
            }
            if (i == slot - 1) {
                select.setMultiple(true);
            }
            select.setSource(ChainedSelect.getSource(config.getApiCode(), values));
            parentId = "${" + name + "}";
            items.add(select);
        }
        String name = Joiner.on("_").join(names);
        Component.FormBase component = ChainedSelect.GroupChainedSelect.build(name, config.getRemark(), true, false, items);
        component.setFormBatProp();
        Object vv = data.get(name);
        if (vv != null) {
            if (vv instanceof Map) {
                component.setValue(((Map<?, ?>) vv).get("value"));
            } else {
                component.setValue(vv);
            }
        }
        return List.of(component);
    }

    private List<String> getSqlDataModelInputs(String dataModelName) {
        DataModel dataModel = getDataModel(dataModelName);
        if (!isSqlModel(dataModel.getModelType())) {
            throw new BusinessException("暂时不支持非SQL类型模型");
        }
        return extractSqlVariables(dataModel.getSqlContent());
    }

    record ChangedFormDef(FormDef formDef, JsonNode rootNode) {
    }

    @Data
    static class BuildResourceRet {
        Set<SysFunctionResourceMapping> addResources;
        Set<DelResource> deleteResources;

        void ofAdd(SysFunctionResourceMapping sysFunctionResourceMapping) {
            if (addResources == null) {
                addResources = new HashSet<>();
            }
            addResources.add(sysFunctionResourceMapping);
        }

        void ofDel(Long resourceId, Collection<String> urls) {
            ofDel(null, resourceId, urls);
        }

        void ofDel(Long formId, Long resourceId, Collection<String> urls) {
            if (deleteResources == null) {
                deleteResources = new HashSet<>();
            }
            if (formId != null) {
                deleteResources.add(DelResource.builder().formId(formId).resourceId(resourceId).urls(urls).build());
            } else {
                deleteResources.add(DelResource.builder().resourceId(resourceId).urls(urls).build());
            }
        }
    }

    @Builder
    record DelResource(Long formId, Long resourceId, Collection<String> urls) {
    }

    @Getter
    @Builder
    static class UrlConvertRet {
        private Set<String> noFromFormIds;
        private Set<FormUrlInfo> noFromFormIdUrlInfos;
        private Map<Long, Set<String>> fromFormIdAndUrls;
        private Map<Long, Set<FormUrlInfo>> fromFormIdUrlInfos;
    }
}
